package ast;

import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.Stack;

public class IfStmt extends Stmt {
	public final CondExpr expr;
	public final Stmt thenstmt, elsestmt;

	public IfStmt(CondExpr e, Stmt s, Location loc) {
		super(loc);
		expr = e;
		thenstmt = s;
		elsestmt = null;
	}

	public IfStmt(CondExpr e, Stmt s1, Stmt s2, Location loc) {
		super(loc);
		expr = e;
		thenstmt = s1;
		elsestmt = s2;
	}

	public void print(PrintStream ps, String indent) {
		ps.print(indent + "if (");
		expr.print(ps);
		ps.print(")\n");
		thenstmt.print(ps, indent + "  ");
		if (elsestmt != null) {
			ps.print("\n" + indent + "else\n");
			elsestmt.print(ps, indent + "  ");
		}
	}

	public void print(PrintStream ps) {
		print(ps, "");
	}

	// interpret: if the condition is true, execute the then statement, else, if the else statement is not null, execute the else statement
	public void execute(Stack<Map<String, AbstractValue>> stateStack, Scanner scanner) throws Exception {

		// evaluate the condition
		AbstractBoolValue cond = expr.evaluate(stateStack, scanner);
		// if the condition of an if-then-else or if-then evaluates to False, the then-branch is guaranteed to be dead code.
		if (cond == AbstractBoolValue.False) {
			// throw dead code exception
			throw new exceptions.ExitDeadCodeException("The then branch of the if-then-else statement is dead code.");
		} else if (cond == AbstractBoolValue.True && elsestmt != null){
			//if the condition of an if-then-else statement evaluates to True, the else-branch is guaranteed to be dead code.
			// throw dead code exception
			throw new exceptions.ExitDeadCodeException("The else branch of the if-then-else statement is dead code.");
		}
		// TODO: if either is dead code, merge them

		// if the condition of an if-then-else statement evaluates to True, the else-branch is guaranteed to be dead code.
		// make a copy of the state
		Stack<Map<String, AbstractValue>> stateStackCopy  = new Stack<Map<String, AbstractValue>>();
		stateStackCopy.addAll(stateStack);
		// for THEN, update original states, no need to merge
		thenstmt.execute(stateStack, scanner);
		// for ELSE, update the copy of the states too, then merge
        if (elsestmt != null) {
            elsestmt.execute(stateStackCopy, scanner);
			this.mergeStates(stateStack.peek(), stateStackCopy.peek());
        }
		return ;
	}

	// TODO: merge the stateCopy into the state
	private void mergeStates(Map<String, AbstractValue> state, Map<String, AbstractValue> stateCopy) {
		for (String key : stateCopy.keySet()) {
			if (state.containsKey(key)) {
				if (stateCopy.get(key) == state.get(key)) {
					state.put(key, stateCopy.get(key));
				} else {
					// if float family, AnyFloat
					if (stateCopy.get(key) == AbstractValue.NegFloat || stateCopy.get(key) == AbstractValue.ZeroFloat || stateCopy.get(key) == AbstractValue.PosFloat || stateCopy.get(key) == AbstractValue.AnyFloat) {
						state.put(key, AbstractValue.AnyFloat);
					}
					// if int family, AnyInt
					else if (stateCopy.get(key) == AbstractValue.NegInt || stateCopy.get(key) == AbstractValue.ZeroInt || stateCopy.get(key) == AbstractValue.PosInt || stateCopy.get(key) == AbstractValue.AnyInt) {
						state.put(key, AbstractValue.AnyInt);
					}
				}
			}
		}
	}
}
